fix(types): SchemaRegistry's kanban entry stops describing a component it cannot name - #7662
Conversation
…t it cannot name
`SchemaRegistry` advertises itself as the Single Source of Truth for component
type lookups, but its `'kanban'` entry named `DeclarativeKanbanSchema` — this
package's authoring/validation face — while the renderer registered for that
key is `ObjectKanbanRenderer` in `@object-ui/plugin-kanban`, which consumes
that package's own `KanbanSchema`. Unrelated dialects, so for this one key the
map's value did not describe the component the key names (objectui#7645).
Correcting the value is not reachable from this layer, measured rather than
assumed: importing the plugin is a phantom dependency (`check:phantom-deps`
rejects the pair, type-only included), and declaring the dependency closes the
cycle `@object-ui/types` -> `@object-ui/plugin-kanban` -> `@object-ui/types`.
objectui#6172's ruling kept the plugin's bare names rather than relocating the
dialect into this zero-workspace-dependency layer, so the gap is by decision.
The entry therefore asserts only what this layer can prove and what both
dialects satisfy: `BaseSchema & { type: 'kanban' }`. `keyof SchemaRegistry` is
unchanged — removing the key would silently narrow the published
`ComponentType` union — and two compile-time pins hold that shape: one here
(the key survives; the value no longer claims the declarative face) and one in
`@object-ui/plugin-kanban`, the only package that can name both sides, proving
the renderer's own `KanbanSchema` satisfies what the entry now asserts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
契约复核 — 裁决:REFUSE(逐字采纳)归属: 档位与核验:隔离 fable 子代理(⛔ 契约复核不适用额度耗尽豁免降档)。transcript 中 harness 逐消息盖章的 独立性:只喂卡片 #7645、既有裁定与 PR 本身;⛔ 未喂派发简报、⛔ 未喂本席结论;简报按对抗性写法。 ⛔ 一处已知渲染损耗:下文有三处尖括号泛型( Contract review — objectui PR #7662 (card #7645)Head ① Derived judgments
② Semver gradingDeclared: Level: correct. AGENTS.md §版本号策略 line 239 is the standing rule: "changeset 里不要声明
③ Boundary-flag disposition
Verdict
Generated by Claude Code |
…nt any-read Contract review (PR #7662) refused the changeset alone: the code and pins were judged sound and `minor` correct, but the release-note input claimed the two kanban dialects share "only the type tag" (measured: 3 shared names of 7 vs 19 body members - type, columns, onCardMove), called a measured compile break "a correction rather than a break", and omitted that BaseSchema's index signature turns the declarative members' reads into silent any. Re-measured on the merge-base and head trees with lit controls before this rewrite: SchemaRegistry['kanban'] -> DeclarativeKanbanSchema compiles at base and is TS2741 at head; SchemaRegistry['kanban']['columns'] is DeclarativeKanbanColumn[] at base and any at head. The changeset now states both effects and keeps the bump at minor per the version-alignment rule. No source or test file changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract re-review (round 2) —
|
…664 (a), not permanent
Wording only. The changeset, the comment above SchemaRegistry['kanban'] in
registry.ts, and both pin docblocks said the types-vs-plugin kanban gap was
"permanent by decision". objectui#6172's ruling (2026-08-31) kept the plugin's
bare names; objectui#7664's ruling (a) (2026-09-05T02:09:40Z) reverses that
half — the @object-ui/types 'kanban' arm is rewritten to the plugin's shape,
plugin-kanban conforms to it, the DeclarativeKanban* trio retires, and
registry.ts is on its execution list — so this PR's BaseSchema & { type:
'kanban' } entry is the transitional state under that ruling. The plugin pin
also called objectui#7651 "an open maintainer decision"; it was ruled B and
closed not_planned at 2026-09-05T02:09:54Z.
No executable line moves: the TypeScript token stream (trivia dropped) of all
three .ts files is identical before and after, the type line registry.ts:218
is byte-identical, and every file keeps its line count so the pins' cited
lines stay put. Both pins read exit 0 through their declared instruments
(tsc -p packages/types/tsconfig.test.json, tsc -p
packages/plugin-kanban/tsconfig.test.json against the rebuilt dist).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract re-review (round 3) —
|
Fixes #7645
SchemaRegistrydocuments itself as "the Single Source of Truth for componenttype lookups". Its
'kanban'entry namedDeclarativeKanbanSchema, while therenderer registered for that key is
ObjectKanbanRendererin@object-ui/plugin-kanban, which consumes that package's ownKanbanSchema.For this one key the map's value did not describe the component the key names.
Option taken: B — the value stops asserting; the key is untouched. The
entry is now
BaseSchema & { type: 'kanban' }: the strongest claim this layercan prove, and one that is true of both dialects.
Why A was unreachable — three routes, all measured
A1 — import the honest type.
check:phantom-deps, exit 1:Baseline for that reading, same gate, unmutated tree:
✅ Every in-scope import is declared by the package that publishes it.(exit 0). Type-onlyimports are not exempt — the gate names the pair explicitly.
A2 — declare the dependency instead. Read off the manifests:
@object-ui/typeshas 0 workspace runtime deps, and
@object-ui/plugin-kanbandeclares@object-ui/types. Adding the edge closes the cycle@object-ui/types -> @object-ui/plugin-kanban -> @object-ui/types. (Readerproven live by the same pass returning 7 workspace deps for plugin-kanban.)
A3 — name a proxy this layer can already reach.
@objectstack/spec/uiis adeclared dependency of
@object-ui/typesand does exportObjectKanbanProps,mirroring the same renderer. It is not the same type: of the plugin
KanbanSchema's 19 members it shares 11, cannot express 8 (allowCollapse,cardTemplates,className,columnWidths,limit,onCardMove,onQuickAdd, and thetypediscriminant every other registry value carries),and adds 2 the plugin does not have (
filter,titleField). Pointing thereswaps one false claim for a differently false one, so it was rejected.
Relocating the plugin dialect into this layer is the remaining route. At the
time of the code commit it was the one objectui#6172's ruling (2026-08-31) had
declined, and the dispatch ruled out widening the published surface for a
latent defect, so it was not taken here. Since then objectui#7664's ruling (a)
(2026-09-05T02:09:40Z) has taken exactly that route — see "The record moved"
below; nothing of it is implemented in this PR.
Latency re-verified, with a lit control
The card's claim that nothing indexes the value side was re-measured, not
inherited. Repo-wide, excluding
node_modulesanddist,SchemaRegistry[returns exactly 1 hit — prose in
.changeset/3965-mint-box-neutral-container.md,not a TypeScript indexed access. Control for the query shape, same command,
same file set:
ComponentPropsMap[returns 39. An earlier control for thismeasurement read zero; it was discarded as a dark instrument and rebuilt rather
than published. Also measured: 0 gate scripts mention
SchemaRegistry(control: 36 hits for
packages/types/srcin the same corpus), and the@object-ui/typesComponentTypehas no in-repo consumer but its owndeclaration.
The pins, and proof they are lit
Type-level assertions are erased at runtime, so vitest says nothing about them.
The instruments are
tsc -p packages/types/tsconfig.test.jsonandtsc -p packages/plugin-kanban/tsconfig.test.json, both chained off theirpackages'
type-checkscripts. Program membership proved with--listFiles:each pin file appears once; the plugin project resolves
@object-ui/typestopackages/types/dist/index.d.ts(0 hits for the source path), so@object-ui/typeswas rebuilt before every reading on that side.Three ablation legs, prediction written before each run:
'kanban': DeclarativeKanbanSchema)_ValueIsNotTheDeclarativeFace…test.ts(77,7): error TS2344— that pin, only that pin'kanban'key_KeyKept_KeyKept) plus TS2339 at 77/83/89types_PluginFaceSatisfiesTheEntry…test.ts(69,7): error TS2344No prediction missed. Every mutation was proved on disk by anchored
before/after counts and
git hash-objectmovement off the HEAD blob; everyrestore by blob equality and an empty
git diff HEAD. Leg 4's restore legalso rebuilt and re-checked, confirming the marker left
dist(1 -> 0) and theplugin project returned to 0 errors — an unrebuilt restore would have left
every later reading measuring the wrong tree. One earlier attempt at leg 1
mutated only half of its target; the count-based guard caught it, the trap
restored, and it was re-run rather than quietly repeated.
Leg 3 is the one that matters most for the published surface: it is the proof
that
keyof SchemaRegistrystill yields'kanban', and that deleting the key— which also removes the false claim and leaves every runtime suite green — is
now a loud compile error.
Verification, at commit
41ff58ffUnion re-run after the final commit, on a clean tree. Each gate's own verdict
line:
check:phantom-deps—✅ Every in-scope import is declared by the package that publishes it.check:control-bytes—✅ check-control-bytes: OK (scanned 6255 tracked text file(s); skipped 85 binary).check:changeset-presence—✅ 3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)check:changeset-no-major— verdict: no changeset declares a major bumpcheck:doc-types—✅ Every documented component type is registered.check:readme-exports—✅ check-readme-exports: OK (… 0 unbuilt …)check:dist-completeness—✓ dist completeness: 12 package(s) complete (1609 emitted files verified)check:spec-symbols—✅ spec symbol derivation: 1344 files scanned against 4959 spec export namescheck:node-esm-load,check:published-dist,check:entry-guard— exit 0pnpm --filter @object-ui/types run type-checkand the same for@object-ui/plugin-kanban— exit 0pnpm exec vitest run packages/types/ packages/plugin-kanban/— 126 files,1844 tests passed
eslinton the three touched files — exit 0check:readme-exportsfirst reportedthe population COLLAPSED -- this run proves nothing(24 of 40 packages unbuilt locally). That is a preconditionfailure, not a red: all packages were built and it was re-run, which is the
green quoted above (
0 unbuilt, 421 self-imports judged, up from 312unjudgeable).
Declared narrowing.
check:sdui-registration-pinsis not measured here— it reads the built console bundle and refuses to judge without one
(
a run with nothing to read has measured nothing). It is not implicated: thediff changes no registration. The full repo-wide lint and the remaining gate
farm are left to CI, which runs them unfiltered. Exit codes throughout were
captured by redirect before any pipe.
Contract review
Clause-②: yes.
SchemaRegistryis published, and this changes the type ofone of its members — a falsifiable contract-semantics claim, not a spelling. It
adds no exported symbol and no key (the diff contains 0 changed
exportlines), so
keyof SchemaRegistryand the publishedComponentTypeunion arebyte-identical; what moves is the value side of one key.
needs:contract-reviewis on both carriers and this stays a draft — notready, not enqueued.
Semver:
minor, and this is a breaking change for consumers outside thisrepository. It ships as
minorbecause this repo's version-alignment rule(AGENTS.md §版本号策略) pins objectui's major to
@objectstack's and shipsobjectui's own breaking changes as
minorwith the break spelled out in thechangeset body — which, after the contract-review remediation below, it is:
the assignability break for consumers who indexed
SchemaRegistry['kanban'],and the silent
anyon member reads.Remediation after contract review — commit
3748de9aThe isolated contract reviewer REFUSED on the changeset alone (code and pins
judged sound, bump level correct). Three defects in that one file, each
re-measured here before rewriting — merge-base tree
2ce2612dand head tree41ff58ff,@object-ui/typesrebuilt on both, predictions written before eachrun, instrument proved to read
packages/types/dist/index.d.ts(1 hit, 0 forsrc/) by--listFiles:on both trees since neither source moved): declarative face 7 body
members, plugin face 19, shared names 3 —
type,columns,onCardMove.onCardMoveis type-identical on both sides (probe_A_OnCardMoveEqualpasses on both trees; only parameter names differ);column element types are 6 vs 6 sharing 5. Controls:
DeclarativeKanbanCardread 9, self-intersection read 7, a disjoint pair read 0. The changeset's
"sharing only the
typetag" was false and is corrected.const s: DeclarativeKanbanSchema = registryValuecompiles at base and fires TS2741 at head (
Property 'columns' is missing). Lit controls fired on both trees: aGridSchemavalue and anumberinto the same target, TS2322 each. The changeset now names this asa break for consumers who indexed
SchemaRegistry['kanban']; the bump staysminorper the repo rule.SchemaRegistry['kanban']['columns']isDeclarativeKanbanColumn[]at base andanyat head (probe pair_C_ColsIsAny/_C_ColsIsDecl, each the other's cross-control — exactlyone fires per tree);
draggablelikewise. Undeclared keys already readanyat base through the sameBaseSchemaindex signature (control passeson both trees), so the change is to the declared members. The changeset now
states the mechanism.
Probe totals matched prediction exactly — 5 errors at head, 6 at base, no
missed prediction. The reviewer's own recorded miss (item 5) reproduced:
const k: KanbanSchema = registryValuecompiles on both trees, and thereverse (plugin face into the registry value) is TS2322 at base —
columnsoptional vs required — and compiles at head. So the plugin-side pin proves the
tag and nothing narrower; neither the changeset nor this body claims more for
it.
Remediation diff,
git diff --stat 41ff58ff7..3748de9af:.changeset/7645-schema-registry-kanban-honesty.md | 46 ++++++++++++++++++-----— 1 file, no source or test file moved. Gates re-run on the committed head
3748de9a, each's own verdict line:check-changeset-no-major—✅ No changeset declares amajorbump.;check-changeset-overwrite—✅ No pre-existing changeset was modified or deleted.;check-changeset-presence—✅ 3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s);check:control-bytes—✅ check-control-bytes: OK (scanned 6255 tracked text file(s); skipped 85 binary).;check:shell-escape-residue—
✅ check-shell-escape-residue: OK. Session for this remediation:https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3.The record moved — round-2 contract review, commit
55a7470e9The second isolated review (PR comment 5548793740) reproduced all twelve of the
first review's judgments and graded the code and the
minorbump correct, thenREFUSED on one factual claim that went false while it was being written: the
changeset, the comment above the entry in
registry.ts, and both pin docblockssaid the types-vs-plugin gap was "permanent by decision". The record:
director seat, maintainer verbatim 「同意」, decision batch Add 8-directional resize system and premium Tailwind design to designer #41). Under (a) the
@object-ui/types'kanban'arm is rewritten to the plugin's shape,@object-ui/plugin-kanbanconforms to that declaration, theDeclarativeKanban*trio retires under ADR-0049, andregistry.tsis on theexecution list. The ruling itself notes that it reverses the "keep both faces"
half of objectui#6172 (2026-08-31). So this PR's
BaseSchema & { type: 'kanban' }entry is the transitional state under that ruling — honest at this commit,
scheduled to be re-pointed at the declared type — not a permanent one. The
remediation commit
3748de9af(02:18:01Z) post-dated the ruling by 8m21s andcarried the stale phrase. Nothing of (a) is implemented here.
not_planned(ruled B, 2026-09-05T02:09:54Z), not"an open maintainer decision".
Commit
55a7470e9corrects the tense in all four carriers in the tree — thechangeset paragraph,
registry.ts(comment above the entry only), the types pindocblock, the plugin pin docblock and its
_StillTwoDialectscomment (the latterbeyond the review's five items: "if these ever converge" is now a scheduled
event, same class) — and this body corrects the two claims above. Every tense
was written after reading the card's current state and its ruling comment.
Wording only, proved: the TypeScript token stream (scanner with trivia —
whitespace and comments — dropped) of each of the three
.tsfiles isidentical before and after, 496 / 231 / 212 tokens, 0 differing; lit control:
the same instrument on a scratch copy with
'kanban'spelled'kanbam'on thetype line reports exactly one differing token.
registry.ts:218isbyte-identical (sha256
7fbfa7bd…before and after) and every code file keepsits line count (228 / 94 / 84), so the pins' cited lines (69/72/77/83/89) are
where the prior evidence cites them. Blob ids moved for all four files, so the
comparison was of changed files. Phrase census after the edit, predictions
written first: "permanent by decision" 0 (from 3), "open maintainer decision"
0 (from 1),
76645 (from 0); one prediction missed — the lit control for thecensus (
TRANSITIONAL) read 2 where 3 was predicted (the plugin pin says"schedules their convergence" and the changeset uses lowercase).
Both pins still read exit 0 through their declared instruments on the
committed head
55a7470e9(dirty-files 0),@object-ui/typesrebuilt first:tsc -p packages/types/tsconfig.test.json --listFiles— exit 0, 0 errors, pinfile 1,
registry.ts1;tsc -p packages/plugin-kanban/tsconfig.test.json --listFiles— exit 0, 0 errors, pin file 1,packages/types/dist/index.d.ts1,
packages/types/src/0. Lit control per instrument, prediction first: ascratch
__lit7645__.test.tsassertingEqualof 1 and 2 dropped into each__tests__dir — exit 2, exactly 1 error,error TS2344at(3,24)in thatfile, both projects; removed,
git statusback to the four edited files only.Gate union on
55a7470e9, each gate's own verdict line:check:control-bytes—
✅ check-control-bytes: OK (scanned 6255 tracked text file(s); skipped 85 binary).;check-changeset-presence—✅ 3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s);check-changeset-no-major—✅ No changeset declares a `major` bump.;check-changeset-overwrite—✅ No pre-existing changeset was modified or deleted.;check:shell-escape-residue—
✅ check-shell-escape-residue: OK; vitest on the two pin files —Test Files 2 passed (2); eslint on the three.tsfiles — exit 0.check:phantom-depsisnot re-run: the import token streams are unchanged (declared narrowing). Exit
codes captured by redirect before any pipe.
For the PM, not edited here: #7664's own sequencing note calls PR #7662
"already merged per the card"; it is open and draft.
Scope
Untouched, as fenced:
packages/plugin-kanban's ownKanbanSchema(the faceobjectui#6172 kept — the new file there only reads it),
KanbanSchema.data(objectui#7651 — ruled B and closed as
not_plannedat 2026-09-05T02:09:54Z:no record-source ladder,
datastays a raw-row input; nothing here reads it),scripts/check-doc-links.mjsandscripts/github-slug.mjs(#7644), and thegetDataConfigproducers (#7632).The card also asks whether the one-authority family rule should be extended so
that every
SchemaRegistryvalue must be the type its registered rendererhonours. That is not answered here: this PR settles the
'kanban'key, and thegeneralization lives on as objectui#7665 (open,
pm:queuewhen this body waswritten), which takes the cheap option C — narrow the docblock's promise to the
key set — while the sweep of the other keys stays unmeasured.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3